<?php

class raster
{
	public $debug = false;
	public $debug_pixel = false;
	
	private $r;
	private $g;
	private $b;
	private $a;
	
	protected $depthBuffer = array();
	
	public $resource;
	public $width;
	public $height;	
	
	public function createResource($width,$height,$r=0,$g=0,$b=0,$a=1)
	{
		$this->width = $width;
		$this->height = $height;
		
		$this->resource = imagecreatetruecolor($width,$height);
		imagealphablending($this->resource, false );
		$transparent = imagecolorallocatealpha($this->resource, $r, $g, $b, $this->convertToAlpha($a));
		imagefilledrectangle($this->resource, 0, 0, $width, $height, $transparent);
		imagealphablending($this->resource, true);

		$this->clearDepthBuffer();
	}

	protected function convertToAlpha($a)
	{
		return 127 * (1 - $a);	// $a = 1.0 = Opaque to 0.0 = transparent		
	}
	
	protected function convertFromAlpha($a)
	{
		return 1 - ($a / 127);
	}	
	
	public function render($filename = null)
	{
		imagealphablending($this->resource, false);
		imagesavealpha($this->resource, true);
		imagePNG($this->resource,$filename);
	}

	public function destroy()
	{
		imageDestroy($this->resource);
	}
		
	public function createColour($r,$g,$b,$a = 1)
	{
		return imagecolorallocatealpha($this->resource,$r,$g,$b,$this->convertToAlpha($a));
	}	
	
	public function getDepth($x,$y)
	{
		return $this->depthBuffer[$y][$x];
	}
	
	public function setDepth($x,$y,$z)
	{
		$this->depthBuffer[$y][$x] = $z;
	}
	
	private function clamp($value,$min,$max)
	{
		if($value < $min)
			return $min;
		else if($value > $max)
			return $max;
		return $value;
	}
	
	protected function drawPixel($x,$y,$z,$colour)
	{
		if($x < 0 || $x >= $this->width || $y < 0 || $y >= $this->height)
			return;
		
		$cz = $this->getDepth($x,$y);
		
		//var_dump($z);
		$cc = imagecolorsforindex($this->resource,imagecolorat($this->resource, $x, $y));
		$cc['alpha'] = $this->convertFromAlpha($cc['alpha']);
		$nc = imagecolorsforindex($this->resource,$colour);
		$nc['alpha'] = $this->convertFromAlpha($nc['alpha']);
		
		
		if($this->debug_pixel)
		{
			echo ("Z: ".$z." vs CZ: ".$cz." na: ".$nc['alpha']." ca: ".$cc['alpha']);
		}		
		
		if(($z < $cz && $nc['alpha'] == 1) || $cc['alpha'] == 0) 			// NORMAL DRAW
		{
			if($this->debug_pixel)
			{
				echo (" NORMAL\n");
			}
			$this->setDepth($x,$y,$z);
			imagesetpixel($this->resource,$x,$y,$colour);	
		} else if($cz < $z && $cc['alpha'] == 1 || $nc['alpha'] == 0) 	// DONT DRAW
		{
			if($this->debug_pixel)
			{
				echo (" NONE\n");
			}
		} else {									// ALPHA BLEND DRAW
			if($this->debug_pixel)
			{
				echo (" ALPHA\n");
			}
			$new = new cPoint3($nc["red"],$nc["green"],$nc["blue"],1);
			$new = cMatrix::scale($new,$nc['alpha']);
			$new->set(0,3,$nc['alpha']);
				
			$current = new cPoint3($cc["red"],$cc["green"],$cc["blue"],1);
			$current = cMatrix::scale($current,$cc['alpha']);
			$current->set(0,3,$cc['alpha']);
				
			$r = cMatrix::add($new,$current);
			$r->set(0,0,$this->clamp($r->get(0,0),0,255));
			$r->set(0,1,$this->clamp($r->get(0,1),0,255));
			$r->set(0,2,$this->clamp($r->get(0,2),0,255));
			$r->set(0,3,$this->clamp($r->get(0,3),0,1));
				
			$colour = $this->createColour($r->get(0,0),$r->get(0,1),$r->get(0,2),$r->get(0,3));
	
			if($z < $cz)
				$this->setDepth($x,$y,$z);
				
			imagesetpixel($this->resource,$x,$y,$colour);	
		}
	}
	
	private function plot($x,$y,$z,$brightness)
	{
		//var_dump($this->a,$brightness);die;
		$colour = $this->createColour($this->r,$this->g,$this->b,$this->a * $brightness);
		$this->drawPixel($x,$y,$z,$colour);
	}
		
	public function drawLine(cPoint3 $v1,cPoint3 $v2,$colour)
	{
		$colours = imagecolorsforindex($this->resource,$colour);
		$this->r = $colours["red"];
		$this->g = $colours["green"];
		$this->b = $colours["blue"];
		$this->a = $this->convertFromAlpha($colours["alpha"]);
	
		$line = new cLine($v1,$v2);
		if($line->sX() > $line->sY())
		{				
			for($x = $line->minX();$x < $line->maxX();$x++)
			{		
				$point = $line->getPointForX($x);
				$positiveGap = abs(intval($point->getY()) - $point->getY());
				$negativeGap = 1.0 - $positiveGap;
				
				$this->plot($point->getX(),$point->getY(),$point->getZ(),$negativeGap);
				$this->plot($point->getX(),$point->getY() + 1,$point->getZ(),$positiveGap);			
			}
		} else {			
			for($y = $line->minY();$y < $line->maxY();$y++)
			{
				$point = $line->getPointForY($y);
				$positiveGap = abs(intval($point->getX()) - $point->getX());
				$negativeGap = 1.0 - $positiveGap;
				
				$this->plot($point->getX(),$point->getY(),$point->getZ(),$negativeGap);
				$this->plot($point->getX() + 1,$point->getY(),$point->getZ(),$positiveGap);			
			}
		}

	}
	
	protected function scanline($x1,$x2,$y,$z1,$z2,$colour)
	{
		//var_dump($x1.", ".$x2."::$y::".$z1.", ".$z2);
		if($x1 > $x2)
		{
			$temp = $x2;
			$x2 = $x1;
			$x1 = $temp;
			
			$temp = $z2;
			$z2 = $z1;
			$z1 = $temp;			
		}
		
		
		$dz = $z2 - $z1;
		$dx = $x2 - $x1;
		if($dx != 0)
			$gradient = $dz / $dx;
		else
			$gradient = 0;
			
		$z = $z1;
		for($x=$x1;$x<=$x2;$x++)
		{
			$this->drawPixel($x,$y,$z,$colour);
			$z += $gradient;
		}
	}
		
	protected function compareVector(cPoint3 $a,cPoint3 $b, cPoint3 $c)
	{
		return $a->getY() <= $b->getY() && $b->getY() <= $c->getY();
	}
	
	protected function compareVectors(cPoint3 $a,cPoint3 $b, cPoint3 $c)
	{
		if($this->compareVector($a,$b,$c))
			return array($a,$b,$c);
			
		if($this->compareVector($a,$c,$b))
			return array($a,$c,$b);
			
		if($this->compareVector($b,$a,$c))
			return array($b,$a,$c);
			
		if($this->compareVector($b,$c,$a))
			return array($b,$c,$a);
			
		if($this->compareVector($c,$a,$b))
			return array($c,$a,$b);
			
		if($this->compareVector($c,$b,$a))
			return array($c,$b,$a);
			
		return array($a,$b,$c);
	}
	
	public function drawTri(cPoint3 $v1,cPoint3 $v2, cPoint3 $v3,$colour)
	{
		list($A,$B,$C) = $this->compareVectors($v1,$v2,$v3);
		
		if($this->debug)
		{
			echo $A."\n";
			echo $B."\n";
			echo $C."\n";
		}
		
		$abX = $B->getX() - $A->getX();
		$abY = $B->getY() - $A->getY();
		$abZ = $B->getZ() - $A->getZ();
		if($abY > 0)
		{
			$abGradient = $abX / $abY;
			$abZGradient = $abZ / $abY;
		} else {
			$abGradient = 0;
			$abZGradient = 0;
		}
			
		$acX = $C->getX() - $A->getX();
		$acY = $C->getY() - $A->getY();
		$acZ = $C->getZ() - $A->getZ();
		if($acY > 0)
		{
			$acGradient = $acX / $acY;
			$acZGradient = $acZ / $acY;
		} else {
			$acGradient = 0;
			$acZGradient = 0;
		}
			
		$bcX = $C->getX() - $B->getX();
		$bcY = $C->getY() - $B->getY();
		$bcZ = $C->getZ() - $B->getZ();
		if($bcY > 0)
		{
			$bcGradient = $bcX / $bcY;
			$bcZGradient = $bcZ / $bcY;
		} else {
			$bcGradient = 0;
			$bcZGradient = 0;
		}
		 
		
		// Follow A-B vs A-C
		$start = new cPoint3($A->getX(),$A->getY(),$A->getZ(),$A->getW());
		$end = new cPoint3($A->getX(),$A->getY(),$A->getZ(),$A->getW());
		for($y=(int)$A->getY();$y<=(int)$B->getY();$y++)
		{
			$this->scanline($start->getX(),$end->getX(),$start->getY(),$start->getZ(),$end->getZ(),$colour);
			$start->setX($start->getX() + $abGradient);
			$start->setY($start->getY() + 1);
			$start->setZ($start->getZ() + $abZGradient);
			$end->setX($end->getX() + $acGradient);
			$end->setY($end->getY() + 1);
			$end->setZ($end->getZ() + $acZGradient);
		}
	
		// Follow B-C vs A-C
		$start = new cPoint3($B->getX(),$B->getY(),$B->getZ(),$B->getW());
		for($y=(int)$B->getY();$y<(int)$C->getY();$y++)
		{
			$this->scanline($start->getX(),$end->getX(),$start->getY(),$start->getZ(),$end->getZ(),$colour);
			$start->setX($start->getX() + $bcGradient);
			$start->setY($start->getY() + 1);
			$start->setZ($start->getZ() + $bcZGradient);
			$end->setX($end->getX() + $acGradient);
			$end->setY($end->getY() + 1);
			$end->setZ($end->getZ() + $acZGradient);
		}	
 	}

	public function clearDepthBuffer()
	{
		for($x=0;$x<$this->width;$x++)
			for($y=0;$y<$this->height;$y++)
				$this->depthBuffer[$x][$y] = PHP_INT_MAX;
	}

	public function drawQuad(cPoint3 $v1,cPoint3 $v2, cPoint3 $v3,cPoint3 $v4,$colour)
	{
		$this->drawTri($v1,$v2,$v3,$colour);
		$this->drawTri($v1,$v3,$v4,$colour);
	}
}

?>